use std::fs;

#[derive(Default, Debug)]
struct Grid {
    numbers: [[i32 ; 5]; 5],
    marked:  [[bool; 5]; 5],
    win: bool,
}

impl Grid {
    fn mark_number(&mut self, number: i32) {
        if self.win { return; }
        for row in 0..5 {
            for col in 0..5 {
                if self.numbers[row][col] == number {
                    self.marked[row][col] = true;
                    return ();
                }
            }
        }
    }

    fn win(&self) -> bool {
        self.win
    }

    fn is_win(&mut self) -> bool {
        if self.win { return self.win; }
        // Check row
        let win: bool = self.marked.iter().any(|row_marked| row_marked.iter().all(|&v| v));
        if win { 
            self.win = true;
            return true; 
        }

        // Check col
        for col in 0..5 {
            let mut win = true;
            for row in 0..5 {
                if self.marked[row][col] == false {
                    win = false;
                    break;
                }
            }
            if win { 
                self.win = true;
                return true; 
            }
        }

        false
    }

    fn unmarked_sum(&self) -> i32 {
        let mut result = 0;
        for row in 0..5 {
            for col in 0..5 {
                if self.marked[row][col] == false {
                    result += self.numbers[row][col];
                }
            }
        }
        result
    }
}

impl From<&str> for Grid {
    fn from(value: &str) -> Self {
        let mut grid = Grid::default();

        for (row, line) in value.lines().enumerate() {
            let line_numbers: Vec<i32> = 
                line.split_whitespace().map(|line| line.parse().unwrap()).collect();
            for col in 0..5 {
                grid.numbers[row][col] = line_numbers[col];
            }
        }

        grid
    }
}

fn parse_input() -> (Vec<i32>, Vec<Grid>) {
    let input = fs::read_to_string("./day04/input.txt").unwrap();
    
    let numbers: Vec<i32> = input.lines()
                                 .nth(0).unwrap()
                                 .split(',')
                                 .map(|t| t.parse().unwrap())
                                 .collect();

    let grids: Vec<Grid> = input.split("\r\n\r\n").skip(1).map(|s| s.into()).collect();

    (numbers, grids)
}

#[allow(unused)]
fn part1() {
    let (numbers, mut grids) = parse_input();

    for number in numbers {
        grids.iter_mut().for_each(|f| f.mark_number(number));

        for grid in grids.iter_mut() {
            if grid.is_win() {
                println!("Part1: {}", grid.unmarked_sum() * number);
                return;
            }
        }
    }
}

#[allow(unused)]
fn part2() {
    let (numbers, mut grids) = parse_input();
    let mut winner_count: usize = 0;
    let target_count = grids.len();

    for number in numbers {
        grids.iter_mut().for_each(|f| f.mark_number(number));

        let mut win_grids: Vec<usize> = Vec::new();
        for grid in grids.iter_mut() {
            if !grid.win() {
                if grid.is_win() {
                    winner_count += 1;
                    if winner_count == target_count {
                        println!("Part2: {}", grid.unmarked_sum() * number);
                        return;
                    }
                }
            }
        }
    }
}

fn main() {
    part1();
    part2();
}
